using System;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.IO;

namespace usbburn
{
    public class LowLevelIO : IDisposable
    {
        const uint FILE_SHARE_READ = 0x00000001;
        const uint FILE_SHARE_WRITE = 0x00000002;
        const uint FILE_SHARE_DELETE = 0x00000004;
        const uint OPEN_EXISTING = 3;

        const uint GENERIC_READ = (0x80000000);
        const uint GENERIC_WRITE = (0x40000000);

        const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
        const uint FILE_READ_ATTRIBUTES = (0x0080);
        const uint FILE_WRITE_ATTRIBUTES = 0x0100;
        const uint ERROR_INSUFFICIENT_BUFFER = 122;

        const uint FILE_FLAG_RANDOM_ACCESS = 0x10000000;
        const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;

        const uint OBJ_CASE_INSENSITIVE = 0x00000040;
        const uint FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;

        const uint SYNCHRONIZE = 0x00100000;

        const uint FSCTL_LOCK_VOLUME = 0x00090018;
        const uint FSCTL_UNLOCK_VOLUME = 0x0009001c;

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        public static extern SafeFileHandle CreateFile(
              string lpFileName,
              uint dwDesiredAccess,
              uint dwShareMode,
              uint SecurityAttributes,
              uint dwCreationDisposition,
              uint dwFlagsAndAttributes,
              int hTemplateFile
              );

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool WriteFile(SafeHandle hFile, byte[] lpBuffer,
           int nNumberOfBytesToWrite, out int lpNumberOfBytesWritten,
           IntPtr lpOverlapped);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool ReadFile(SafeHandle hFile, byte[] lpBuffer,
           int nNumberOfBytesToRead, out int lpNumberOfBytesRead, IntPtr lpOverlapped);

        [DllImport("kernel32.dll", SetLastError = true)]
        internal static extern bool DeviceIoControl(
            SafeFileHandle hDevice,
            uint dwIoControlCode,
            byte[] lpInBuffer,
            int nInBufferSize,
            byte[] lpOutBuffer,
            int nOutBufferSize,
            ref int lpBytesReturned,
            IntPtr lpOverlapped);

        private SafeFileHandle handleValue = null;

        public LowLevelIO(string Path, FileAccess fa)
        {
            uint flags = 0;
            switch (fa)
            {
                case FileAccess.Read:
                    flags = GENERIC_READ;
                    break;
                case FileAccess.Write:
                    flags = GENERIC_WRITE;
                    break;
                case FileAccess.ReadWrite:
                    flags = GENERIC_WRITE | GENERIC_READ;
                    break;
                default:
                    throw new ArgumentException();
            }

            handleValue = CreateFile(Path, flags, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);

            if (handleValue.IsInvalid)
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

            int bytes = 0;
            if(!DeviceIoControl(handleValue, FSCTL_LOCK_VOLUME, null, 0, null, 0, ref bytes, IntPtr.Zero))
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }

        public void Dispose()
        {
            int bytes = 0;
            if (!DeviceIoControl(handleValue, FSCTL_UNLOCK_VOLUME, null, 0, null, 0, ref bytes, IntPtr.Zero))
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

            handleValue.Dispose();
        }

        public SafeFileHandle Handle
        {
            get
            {
                return handleValue;
            }
        }

        public byte[] Read(int size)
        {
            int read = 0;
            byte[] buf = new byte[size];
            if(!ReadFile(handleValue,buf,buf.Length,out read,IntPtr.Zero))
               Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

            if (read < size)
            {
                byte[] cp = new byte[read];
                System.Array.Copy(buf, cp, read);
                return cp;
            }
            else
            {
                return buf;
            }
        }

        public void Write(byte[] buf)
        {
            int write = 0;
            if(!WriteFile(handleValue,buf,buf.Length,out write,IntPtr.Zero))
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            if(write!=buf.Length)
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }

    }
}